﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using TimScripts.BF4Rcon.Resources;

namespace TimScripts.BF4Rcon
{
    public class Map : IMap
    {
        private static IDictionary<MapName, MapInformationAttribute> MapData = GenerateData<MapName, MapInformationAttribute>();
        private static IDictionary<GameMode, GameModeInformationAttribute> GameModeData = GenerateData<GameMode, GameModeInformationAttribute>();
        private static IDictionary<string, MapName> RawMapNameData = GenerateRawData(MapData, a => a.RawName);
        private static IDictionary<string, GameMode> RawGameModeData = GenerateRawData(GameModeData, a => a.RawName);

        public MapName MapName
        {
            get;
            private set;
        }

        public GameMode GameMode
        {
            get;
            private set;
        }

        public string DescriptiveMapName
        {
            get { return MapData[MapName].DescriptiveName; }
        }

        public string RawMapName
        {
            get { return MapData[MapName].RawName; }
        }

        public string DescriptiveModeName
        {
            get { return GameModeData[GameMode].DescriptiveName; }
        }

        public string RawModeName
        {
            get { return GameModeData[GameMode].RawName; }
        }

        public int Players
        {
            get { return GameModeData[GameMode].Players; }
        }

        public int Commanders
        {
            get { return GameModeData[GameMode].Commanders; }
        }

        public int Spectators
        {
            get { return GameModeData[GameMode].Spectators; }
        }

        public int RoundsToPlay { get; private set; }

        public static Map Parse(string rawMapName, string rawGameMode, int numRounds)
        {
            MapName mapName = RawMapNameData[rawMapName];
            GameMode gameMode = RawGameModeData[rawGameMode];
            Map result = new Map(mapName, gameMode, numRounds);

            return result;
        }

        public static Map Parse(string rawMapName, string rawGameMode)
        {
            MapName mapName;
            GameMode gameMode;
            Map result;

            if (!RawMapNameData.TryGetValue(rawMapName, out mapName))
                mapName = MapName.Unknown;

            if (!RawGameModeData.TryGetValue(rawGameMode, out gameMode))
                gameMode = GameMode.Unknown;

            result = new Map(mapName, gameMode);

            return result;
        }

        public Map(MapName mapName, GameMode gameMode)
        {
            MapName = mapName;
            GameMode = gameMode;

            RoundsToPlay = -1;
        }

        public Map(MapName mapName, GameMode gameMode, int roundsToPlay)
        {
            if (roundsToPlay < 1)
                throw new ArgumentOutOfRangeException("roundsToPlay", ErrorStrings.ArgumentOutOfRange_MapRoundsToPlay);

            MapName = mapName;
            GameMode = gameMode;

            RoundsToPlay = roundsToPlay;
        }

        private static Dictionary<TEnum, TAttrib> GenerateData<TEnum, TAttrib>()
            where TAttrib : Attribute
        {
            Type nameType = typeof(TEnum);
            string[] names = Enum.GetNames(nameType);
            FieldInfo target;
            object[] attributes;
            TEnum mapName;
            Dictionary<TEnum, TAttrib> result = new Dictionary<TEnum, TAttrib>(names.Length);

            foreach (string name in names)
            {
                mapName = (TEnum)Enum.Parse(nameType, name, true);

                target = nameType.GetField(name);
                attributes = target.GetCustomAttributes(typeof(TAttrib), false);

                result[mapName] = (TAttrib)attributes[0];
            }

            return result;
        }

        private static IDictionary<string, TEnum> GenerateRawData<TEnum, TAttrib>(IDictionary<TEnum, TAttrib> data, Func<TAttrib, string> rawConverter)
            where TAttrib : Attribute
        {
            Dictionary<string, TEnum> result = new Dictionary<string, TEnum>(data.Count, StringComparer.OrdinalIgnoreCase);
            string raw;

            foreach (var d in data)
            {
                raw = rawConverter(d.Value);

                result[raw] = d.Key;
            }

            return result;
        }
    }
}
